// Copyright © 2009, Blake Coverett 
// Use and Redistribute Freely under the Ms-PL
// http://www.opensource.org/licenses/ms-pl.html
//
// Modified by Igor Ostrovsky
base.require('html');
var robozzle = (function() {
    var module = {};
    var width, height, maxSubs, maxSubLength;
    var directions, cellSprites, programSprites;
    var puzzle, board = [], program = [];
    var col, row, dir, stars, steps, stack;
    var centerX, activeBlock, continueRunning, buttons = {};
    var $ = html.$;

    var SetSprite = function(elem, sprite) {
        elem.style.backgroundPosition = sprite;
    }
    var MergeSprites = function(row, col) {
        var r = row.split(' ');
        var c = col.split(' ');
        return c[0] + ' ' + r[1];
    }

    var SetCell = function(x, y, c) {
        SetSprite(board[y][x], cellSprites[c]);
        board[y][x].puzzleColor = c.charAt(0).toLowerCase();
        if (c >= 'A' && c <= 'Z') {
            board[y][x].puzzleStar = true;
            ++stars;
        } else
            board[y][x].puzzleStar = false;
    }
    var GetCell = function(x, y) {
        return { color: board[y][x].puzzleColor, star: board[y][x].puzzleStar };
    }
    var ClearContent = function(x, y) {
        SetCell(x, y, board[y][x].puzzleColor);
    }
    var SetRobot = function() {
        SetCell(col, row, board[row][col].puzzleColor + dir);
    }
    var SetProgramBlock = function(block, cmd) {
        cmd = cmd.toLowerCase().split('-');
        var predicate, action;
        if (cmd.length == 1) {
            if (cmd[0] == 'nop') {
                SetSprite(block, programSprites['nop']);
                block.puzzlePredicate = 'nop';
                block.puzzleAction = 'nop';
                return;
            }
            predicate = 'grey';
            action = cmd[0];
        } else {
            predicate = cmd[0];
            action = cmd[1];
        }

        if (action == 'red' || action == 'green' || action == 'blue') action = 'paint' + action;
        SetSprite(block, programSprites[predicate + '-' + action]);
        block.puzzlePredicate = predicate;
        block.puzzleAction = action;
    }
    var SetProgram = function(func, step, cmd) { SetProgramBlock(program[func][step], cmd); }
    var HighlightStep = function(func, step, showHighlight) {
        program[func][step].innerHTML = showHighlight ? "__" : '';
    }

    var GetProgramString = function() {
        var programString = '';
        for (var func = 0; func < maxSubs; ++func) {
            for (var step = 0; step < maxSubLength; ++step) {
                var action = program[func][step].puzzleAction;
                if (action != "nop") {

                    programString += encodedPredicates[program[func][step].puzzlePredicate];
                    programString += encodedActions[action];
                }
            }
            programString += "|";
        }
        return programString;
    }

    var actions = {
        nop: function() { return true },
        forward: function() {
            ClearContent(col, row);
            var d = directions[dir];
            row += d.row;
            if (row < 0 || row >= height) return false;
            col += d.col;
            if (col < 0 || col >= width) return false;
            var c = GetCell(col, row);
            if (c.star) --stars;
            SetRobot();
            if (c.color == ' ') return false;
            ++steps;
            return true;
        },
        left: function() { dir = (dir + 3) % 4; SetRobot(); ++steps; return true },
        right: function() { dir = (dir + 1) % 4; SetRobot(); ++steps; return true },
        paintred: function() { SetCell(col, row, 'r'); SetRobot(); ++steps; return true },
        paintgreen: function() { SetCell(col, row, 'g'); SetRobot(); ++steps; return true },
        paintblue: function() { SetCell(col, row, 'b'); SetRobot(); ++steps; return true },
        f1: function() { ++stack[0].step; stack.unshift({ func: 0, step: -1 }); return true; },
        f2: function() { ++stack[0].step; stack.unshift({ func: 1, step: -1 }); return true; },
        f3: function() { ++stack[0].step; stack.unshift({ func: 2, step: -1 }); return true; },
        f4: function() { ++stack[0].step; stack.unshift({ func: 3, step: -1 }); return true; },
        f5: function() { ++stack[0].step; stack.unshift({ func: 4, step: -1 }); return true; }
    };
    var predicates = {
        nop: function() { return false },
        grey: function() { return true },
        red: function() { return GetCell(col, row).color == 'r' },
        green: function() { return GetCell(col, row).color == 'g' },
        blue: function() { return GetCell(col, row).color == 'b' }
    };

    var encodedActions = {
        nop: '_',
        forward: 'F',
        left: 'L',
        right: 'R',
        paintred: 'r',
        paintgreen: 'g',
        paintblue: 'b',
        f1: '1',
        f2: '2',
        f3: '3',
        f4: '4',
        f5: '5'
    };

    var encodedPredicates = {
        nop: '_',
        grey: '_',
        red: 'r',
        green: 'g',
        blue: 'b'
    };

    var PuzzleFinished = function(isDone) {
        continueRunning = false;
        for (b in buttons)
            if (isDone)
            $(b).style.backgroundPosition = buttons[b];
        else
            $(b).removeAttribute('style');
    }

    var Step = function() {
        var func = stack[0].func;
        var step = stack[0].step;
        if (predicates[program[func][step].puzzlePredicate]()) {
            if (!actions[program[func][step].puzzleAction]()) {
                alert("Fell off the puzzle.");
                PuzzleFinished(true);
                return;
            }
            if (stars < 1) {
                alert("Puzzle complete in " + steps + " steps.");
                var params = "solution=" + GetProgramString() + "&levelId=" + puzzle.id;
                base.postAJAX("/js/submit.aspx", params, null);
                PuzzleFinished(true);
                return;
            }
            if (steps > 1000) {
                alert("Puzzle must be completed in no more than 1000 steps.");
                PuzzleFinished(true);
                return;
            }
        }

        HighlightStep(func, step, false);
        do {
            if (++stack[0].step >= maxSubLength) {
                stack.shift();
                if (stack.length < 1) {
                    PuzzleFinished(true);
                    return;
                }
            }
        }
        while (!predicates[program[stack[0].func][stack[0].step].puzzlePredicate]());
        HighlightStep(stack[0].func, stack[0].step, true);
        if (continueRunning) window.setTimeout(Step, 40);
    };

    var StepPuzzle = function() {
        if (this.style.backgroundPosition) return false;
        continueRunning = false;
        Step();
        return false;
    }

    var RunPuzzle = function() {
        if (this.style.backgroundPosition) return false;
        if (!continueRunning) {
            continueRunning = true;
            Step();
        } else
            continueRunning = false;
        return false;
    }

    var ResetPuzzle = function() {
        continueRunning = false;
        window.setTimeout(function() { LoadPuzzle(puzzle); }, 80); //HACK 
        return false;
    }

    var CreateBoard = module.CreateBoard = function(w, h, subs, subLen) {
        width = w; height = h; maxSubs = subs; maxSubLength = subLen;
        directions = [{ row: 0, col: 1 }, { row: 1, col: 0 }, { row: 0, col: -1 }, { row: -1, col: 0}];
        cellSprites = {};
        var colors = { ' ': 'grey', r: 'red', g: 'green', b: 'blue' };
        for (var c in colors) {
            var color = colors[c];
            cellSprites[c] = $('board' + color).style.backgroundPosition;
            if (c != ' ')
                cellSprites[c.toUpperCase()] = MergeSprites(cellSprites[c], $('star').style.backgroundPosition);
            for (var d = 0; d < 4; ++d)
                cellSprites[c + d] = MergeSprites(cellSprites[c], $('robot' + d).style.backgroundPosition);
        }
        programSprites = { 'unavailable': $('unavailableProgram').style.backgroundPosition };
        for (var predicate in predicates)
            for (var action in actions)
            programSprites[predicate + '-' + action] =
					    MergeSprites($(predicate).style.backgroundPosition,
								     $(action == 'nop' ? 'grey' : action).style.backgroundPosition);
        programSprites['grey-nop'] = programSprites['nop'] = $('blankProgram').style.backgroundPosition;

        var rowTemplate = { tr: [] };
        for (var x = 0; x < width; ++x) rowTemplate.tr.push({ td: html.marker });
        for (var y = 0; y < height; ++y) board.push(html.add($('board'), rowTemplate));
        var progTemplate = { tr: [{ th: ['F{num}'], id: 'sub{num}'}] };
        for (var i = 0; i < maxSubLength; ++i) progTemplate.tr.push({ td: html.marker });
        for (var i = 0; i < maxSubs; ++i) program.push(html.add($('program'), progTemplate, { num: i + 1 }));
        base.attach($('runPuzzle'), 'click', RunPuzzle);
        buttons['runPuzzle'] = $('runPuzzle').style.backgroundPosition;
        base.attach($('stepPuzzle'), 'click', StepPuzzle);
        buttons['stepPuzzle'] = $('stepPuzzle').style.backgroundPosition;
        base.attach($('resetPuzzle'), 'click', ResetPuzzle);
        for (var action in actions) {
            base.attach($(action), 'click', (function(cmd) { return function() {
                    var p = activeBlock.puzzlePredicate;
                    SetProgramBlock(activeBlock, ((cmd != 'nop' && p != 'nop') ? p + '-' : '') + cmd);
                    ResetPuzzle();
                }})(action));
        }
        for (var predicate in predicates) {
            if (predicate != "nop")
                base.attach($(predicate), 'click', (function(pred) { return function() {
                        SetProgramBlock(activeBlock, pred + '-' + activeBlock.puzzleAction);
                        ResetPuzzle();
                    }})(predicate));
        }

        centerX = $('robozzle').offsetWidth / 2 + $('robozzle').offsetLeft;
    }

    var OnProgramClick = function(e) {
        var r = $('resources');
        var s = r.style;
        if (s.display == 'none' || activeBlock !== this) {
            activeBlock = this;
            s.display = 'block';
            var d = document.documentElement, b = document.body;
            var x = e.clientX + (d && d.scrollLeft || b.scrollLeft || 0) - (d.clientLeft || 0);
            var y = e.clientY + (d && d.scrollTop || b.scrollTop || 0) - (d.clientTop || 0);
            s.left = (x - (x > centerX ? r.offsetWidth : 0)) + 'px';
            s.top = (y - r.offsetHeight) + 'px';
        } else
            s.display = 'none';
    }

    var LoadPuzzle = module.LoadPuzzle = function(p, solution) {
        puzzle = p;
        $('puzzleTitle').innerHTML = puzzle.title;
        $('puzzleAbout').innerHTML = puzzle.about;
        stars = steps = 0;
        stack = [{ func: 0, step: 0}];
        for (var x = 0; x < width; ++x)
            for (var y = 0; y < height; ++y)
            SetCell(x, y, puzzle.board.charAt(y * width + x));
        col = puzzle.robotCol;
        row = puzzle.robotRow;
        dir = puzzle.robotDir;
        SetRobot();
        for (var func = 0; func < maxSubs; ++func) {
            if (puzzle.subs[func] > 0) {
                $('sub' + (func + 1)).style.display = 'block';
                $('f' + (func + 1)).style.visibility = 'visible';
            } else {
                $('sub' + (func + 1)).style.display = 'none';
                $('f' + (func + 1)).style.visibility = 'hidden';
            }
            for (var step = 0; step < maxSubLength; ++step) {
                if (solution !== undefined) SetProgram(func, step, 'nop');
                try {
                    base.detach(program[func][step], 'click', OnProgramClick);
                } catch (e) { }
                HighlightStep(func, step, false);
                var s = program[func][step].style;
                if (step >= puzzle.subs[func]) {
                    SetSprite(program[func][step], programSprites['unavailable']);
                    s.cursor = 'default';
                } else {
                    s.cursor = 'pointer';
                    base.attach(program[func][step], 'click', OnProgramClick);
                }
            }
        }
        if (solution !== undefined)
            for (var func = 0; func < solution.length; ++func)
            if (solution[func].length > 0) {
            var funcSteps = solution[func].split(' ');
            for (var step = 0; step < funcSteps.length; ++step)
                SetProgram(func, step, funcSteps[step]);
        }
        HighlightStep(stack[0].func, stack[0].step, true);
        $('resources').style.display = 'none';
        $('paintred').style.visibility = (puzzle.allowedCommands & 1) ? 'visible' : 'hidden';
        $('paintgreen').style.visibility = (puzzle.allowedCommands & 2) ? 'visible' : 'hidden';
        $('paintblue').style.visibility = (puzzle.allowedCommands & 4) ? 'visible' : 'hidden';
        PuzzleFinished(false);
    }

    return module;
})();
